#include "mainwindow.h"
#include "bus.h"
#include "debugger.h"
#include "screen.h"
#include "ui_mainwindow.h"

#include <QDateTime>
#include <QFileDialog>
#include <QMessageBox>

Bus Nes; // 全局声明Nes模拟器

static int read_dmc(void *, cpu_addr_t addr)
{
    return Nes.load(addr);
}

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), speaker(this), game_timer(this), ui(new Ui::MainWindow)
{
    // 初始化
    WindowInit();

    ui->setupUi(this);
    this->setFocusPolicy(Qt::StrongFocus);
    ui->centralWidget->setFocusPolicy(Qt::NoFocus);
    ui->screen->setFocusPolicy(Qt::NoFocus);

    // 绑定dmc读取函数
    Nes.Apu.dmc_reader(read_dmc, NULL);

    // 绑定控件行为
    InitActionMenu();

    // 默认开启声音
    ToggleSound();
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::WindowInit()
{
    file_path.clear();

    game_timer.setTimerType(Qt::PreciseTimer);
    connect(&game_timer, &QTimer::timeout, this, &MainWindow::OnNewFrame);
}

void MainWindow::InitActionMenu()
{
    connect(ui->actionExit, &QAction::triggered, this, &MainWindow::close);
    connect(ui->actionChooseFile, &QAction::triggered, this, &MainWindow::OnChooseFile);
    connect(ui->actionOpenDebugger, &QAction::triggered, this, [=]() {
        Debugger *debugger = new Debugger(this);
        debugger->ConnectToBus(&Nes);
        debugger->show();
    });
    connect(ui->actionOpenSound, &QAction::triggered, this, &MainWindow::ToggleSound);
    connect(ui->actionSave, &QAction::triggered, this, &MainWindow::SaveGame);
    connect(ui->actionLoad, &QAction::triggered, this, &MainWindow::LoadGame);
    connect(ui->actionReload, &QAction::triggered, this, &MainWindow::ReloadGame);
}

void MainWindow::FCStart()
{
    ResumeGame();
}

void MainWindow::PauseGame()
{
    if (game_timer.isActive())
        game_timer.stop();
}

void MainWindow::ResumeGame()
{
    // TODO: 只精确到毫秒的定时器没法严格保证是60帧
    if (!file_path.isEmpty())
        game_timer.start(16);
}

void MainWindow::OnChooseFile()
{
    QString filename = QFileDialog::getOpenFileName(this, "Choose File", "./", "Rom (*.nes *.NES)");

    if (filename.isEmpty()) {
        return;
    } else {
        file_path = filename;
        PauseGame();

        Nes.cartridge.reset();
        if (Nes.cartridge.read_from_file(filename)) {
            Nes.reset();
            FCStart();
        } else
            file_path.clear();
    }
}

void MainWindow::ReloadGame()
{
    if (!file_path.isEmpty()) {
        PauseGame();

        Nes.cartridge.reset();
        if (Nes.cartridge.read_from_file(file_path)) {
            Nes.reset();
            FCStart();
        } else
            file_path.clear();
    }
}

void MainWindow::OnNewFrame()
{
    do {
        Nes.clock();
    } while (!Nes.Ppu.frame_complete);
    Nes.Ppu.frame_complete = false;

    // 1帧调用一次APU
    Nes.Apu.end_frame();
    if (OpenSound) {
        Nes.Apu.out_count = Nes.Apu.read_samples(Nes.Apu.out_buf, BUFFER_SIZE);
        speaker.pushSound((char *) Nes.Apu.out_buf, Nes.Apu.out_count * 2);
    }

    ui->screen->update(Nes.Ppu.frame_data);

    this->update(); // trigger screen's paintEvent
}

void MainWindow::ToggleSound()
{
    if (!OpenSound) {
        OpenSound = true;
        ui->actionOpenSound->setText("关闭声音");
    } else {
        OpenSound = false;
        ui->actionOpenSound->setText("打开声音");
    }
}

// 这个函数可以一次性创建多级目录
QString mkMutiDir(const QString path)
{
    QDir dir(path);
    if (dir.exists(path)) {
        return path;
    }
    QString parentDir = mkMutiDir(path.mid(0, path.lastIndexOf('/')));
    QString dirname = path.mid(path.lastIndexOf('/') + 1);
    QDir parentPath(parentDir);
    if (!dirname.isEmpty())
        parentPath.mkpath(dirname);
    return parentDir + "/" + dirname;
}

void MainWindow::SaveGame()
{
    if (!file_path.isEmpty()) {
        mkMutiDir("./save/" + Nes.cartridge.game_title);

        QFile file("./save/" + Nes.cartridge.game_title + "/"
                   + QDateTime::currentDateTime().toString("yyyyMMddhhmmss") + ".sav");
        file.open(QFile::WriteOnly);
        QDataStream output(&file);
        output << Nes;
        file.close();
    }
}

void MainWindow::LoadGame()
{
    if (!file_path.isEmpty()) {
        QString filename = QFileDialog::getOpenFileName(this,
                                                        "Choose File",
                                                        "./",
                                                        "save file (*.sav)");
        if (filename.isEmpty())
            return;
        else {
            QFile file(filename);
            file.open(QIODevice::ReadOnly);
            QDataStream input(&file);
            input >> Nes;
            file.close();
        }
    }
}

void MainWindow::keyPressEvent(QKeyEvent *event)
{
    int key = event->key();
    if (Nes.controller_left.key_map.find(key) != Nes.controller_left.key_map.end()) {
        Nes.controller_left.cur_keystate[Nes.controller_left.key_map[key]] = true;
    } else if (Nes.controller_right.key_map.find(key) != Nes.controller_right.key_map.end()) {
        Nes.controller_right.cur_keystate[Nes.controller_right.key_map[key]] = true;
    }
}

void MainWindow::keyReleaseEvent(QKeyEvent *event)
{
    int key = event->key();
    if (Nes.controller_left.key_map.find(key) != Nes.controller_left.key_map.end()) {
        Nes.controller_left.cur_keystate[Nes.controller_left.key_map[key]] = false;
    } else if (Nes.controller_right.key_map.find(key) != Nes.controller_right.key_map.end()) {
        Nes.controller_right.cur_keystate[Nes.controller_right.key_map[key]] = false;
    }
}

void MainWindow::focusInEvent(QFocusEvent *event)
{
    ResumeGame();
    QMainWindow::focusInEvent(event);
}

void MainWindow::focusOutEvent(QFocusEvent *event)
{
    PauseGame();
    QMainWindow::focusOutEvent(event);
}
